﻿function Engine(context)
{
    this.context = context;

    var _canvas;
    var _visualComponents = new Object();
    var _interval;
    var _componentCount = 0;

    /** Add visual component to the engine **/
    this.add = function (visualComponent)
    {
        _componentCount++;
        visualComponent.id = _componentCount;
        visualComponent.engine = this;
        _visualComponents[_componentCount] = visualComponent;
        return visualComponent;
    }

    /** Removes visual component from the engine **/
    this.remove = function (id) 
    {
        var visualComponent = _visualComponents[id];
        visualComponent.engine = null;
        delete _visualComponents[id];
        _componentCount--;
    }

    /** Starts engine and begins painting **/
    this.start = function () 
    {
        this.context = context;

        _interval = setInterval(function () 
        {
            performUpdates();
        }, 30);
    }

    /** Stops the engine **/
    this.stop = function()
    {
        clearInterval(_interval);
    }

    /** Performs updates to all objects in the engine **/
    function performUpdates()
    {
        var relevantCount = _componentCount;
        var done = new Object;

        for (key in _visualComponents) 
        {
            done[key] = _visualComponents[key];
        }

        //Loop until all components have been moved fully
        while (relevantCount > 0)
        {
            for (key in done) 
            {
                visualComponent = done[key];
                if (visualComponent.update())
                {
                    relevantCount--;
                    delete done[key];
                }
            }

            performCollisionDetection();

            drawScreen();
        }
    }

    /** Performs collision detection checks on all objects in the engine **/
    function performCollisionDetection()
    {
        for (key in _visualComponents) 
        {
            visualComponent = _visualComponents[key];
            for (key2 in _visualComponents) 
            {
                visualComponent2 = _visualComponents[key2];

                if (visualComponent != visualComponent2)
                {
                    if (isPixelCollision(visualComponent.imageData, visualComponent.x, visualComponent.y,
                        visualComponent2.imageData, visualComponent2.x, visualComponent2.y))
                    {
                        //Collided with object
                        visualComponent2.collide(visualComponent);
                    }
                }
            }
        }
    }

    /** Draws all objects to the canvas **/
    function drawScreen() 
    {
        //Clear screen
        this.context.clearRect(0, 0, canvas.width, canvas.height);

        //Loop through all disply objects drawing each
        for (key in _visualComponents) 
        {
            visualComponent = _visualComponents[key];

            //Draw object to screen
            visualComponent.draw();
        }
    }
}



/**
* @author Joseph Lenton - PlayMyCode.com
*
* @param first An ImageData object from the first image we are colliding with.
* @param x The x location of 'first'.
* @param y The y location of 'first'.
* @param other An ImageData object from the second image involved in the collision check.
* @param x2 The x location of 'other'.
* @param y2 The y location of 'other'.
* @param isCentred True if the locations refer to the centre of 'first' and 'other', false to specify the top left corner.
*/
function isPixelCollision(first, x, y, other, x2, y2, isCentred) {
    // we need to avoid using floats, as were doing array lookups
    x = Math.round(x);
    y = Math.round(y);
    x2 = Math.round(x2);
    y2 = Math.round(y2);

    var w = first.width,
        h = first.height,
        w2 = other.width,
        h2 = other.height;

    // deal with the image being centred
    if (isCentred) {
        // fast rounding, but positive only
        x -= (w / 2 + 0.5) << 0
        y -= (h / 2 + 0.5) << 0
        x2 -= (w2 / 2 + 0.5) << 0
        y2 -= (h2 / 2 + 0.5) << 0
    }

    // find the top left and bottom right corners of overlapping area
    var xMin = Math.max(x, x2),
        yMin = Math.max(y, y2),
        xMax = Math.min(x + w, x2 + w2),
        yMax = Math.min(y + h, y2 + h2);

    // Sanity collision check, we ensure that the top-left corner is both
    // above and to the left of the bottom-right corner.
    if (xMin >= xMax || yMin >= yMax) {
        return false;
    }

    var xDiff = xMax - xMin,
        yDiff = yMax - yMin;

    // get the pixels out from the images
    var pixels = first.data,
        pixels2 = other.data;

    // if the area is really small,
    // then just perform a normal image collision check
    if (xDiff < 4 && yDiff < 4) {
        for (var pixelX = xMin; pixelX < xMax; pixelX++) {
            for (var pixelY = yMin; pixelY < yMax; pixelY++) {
                if (
                        (pixels[((pixelX - x) + (pixelY - y) * w) * 4 + 3] !== 0) &&
                        (pixels2[((pixelX - x2) + (pixelY - y2) * w2) * 4 + 3] !== 0)
                ) {
                    return true;
                }
            }
        }
    } else {
        /* What is this doing?
        * It is iterating over the overlapping area,
        * across the x then y the,
        * checking if the pixels are on top of this.
        *
        * What is special is that it increments by incX or incY,
        * allowing it to quickly jump across the image in large increments
        * rather then slowly going pixel by pixel.
        *
        * This makes it more likely to find a colliding pixel early.
        */

        // Work out the increments,
        // it's a third, but ensure we don't get a tiny
        // slither of an area for the last iteration (using fast ceil).
        var incX = xDiff / 3.0,
            incY = yDiff / 3.0;
        incX = (~ ~incX === incX) ? incX : (incX + 1 | 0);
        incY = (~ ~incY === incY) ? incY : (incY + 1 | 0);

        for (var offsetY = 0; offsetY < incY; offsetY++) {
            for (var offsetX = 0; offsetX < incX; offsetX++) {
                for (var pixelY = yMin + offsetY; pixelY < yMax; pixelY += incY) {
                    for (var pixelX = xMin + offsetX; pixelX < xMax; pixelX += incX) {
                        if (
                                (pixels[((pixelX - x) + (pixelY - y) * w) * 4 + 3] !== 0) &&
                                (pixels2[((pixelX - x2) + (pixelY - y2) * w2) * 4 + 3] !== 0)
                        ) {
                            return true;
                        }
                    }
                }
            }
        }
    }

    return false;
}